// From :Eplankton Date: 2021/11/15(Start date)
#ifndef _MATRIX
#define _MATRIX

#include <iostream>
#include <vector>
#include <cmath>
#include <string>

#define _PI 3.141592657
#define _MAX_SCALE 50
#define LIMIT_ZERO 1e-005
#define in Init
#define set Init
#define In Init
#define Set Init
#define NULL_M NULL_MATRIX;

using namespace std;

class matrix
{
public:
    string name;
    int row, column;
    int rank;
    double module;       // Only for  vectors.
    double det;          // Only for cubes.
    vector<double> body; // Contain the actual elements of matrix.

    matrix() : name(""), row(0), column(0) {}
    matrix(const string init_name, const int init_row, const int init_column);
    matrix(const string init_name, const int init_row, const int init_column, const std::initializer_list<double>);
    matrix(int init_row, int init_column);
    matrix(int init_row, int init_column, const std::initializer_list<double>);
    matrix(int N);

    vector<double> &Init(const string init_name, const int init_row, const int init_column); // Initialize a matrix by name, row and column.
    vector<double> &Init(const int init_row, const int init_column);                         // Non-named initialization.

    void Stock_byColumn(void); // Storage a matrix in column-order(default in row-order).
    void Head(void) const;     // Display head information.
    void Out(char key) const;  // Print in format.
    void Out(void) const;      // Print the matrix on screen in non_format.
    void Clear(void);          // Empty the matrix into NULL_MATRIX.
    void Info(void);           // Generate information list.

    double &operator()(unsigned int row, unsigned int column); // Access to element by A(i,j).
    matrix &operator=(const matrix &obj);
    bool operator==(const matrix &latter);  // Whether two matrices are equal.
    matrix operator+(const matrix &latter); // Calculate the sum of matrices.
    matrix operator*(const matrix &second); // Calculate the multiplication of matrices.

    template <typename T>        // The GCC(g++) complier has some werid bugs in templates.
    matrix operator*(const T ax) // Calculate the multiplication of matrix and real_numbers.
    {
        matrix result = *this;
        result.name = "(" + to_string(ax) + "*" + this->name + ")";
        for (int i = 0; i < this->row * this->column; i++)
        {
            result.body[i] *= ax;
        }
        return result;
    }

    template <typename T>                          // I hate templates.
    friend matrix operator*(const T ax, matrix Mt) // FOR GOD SAKE, you have to put the damn definition here.
    {
        return Mt * ax; // Overload again to allow the exchange of position.
    }

    matrix operator-(const matrix &latter); // Calculate the subtract of matrices.
    matrix operator^(const int pow);        // Overload for Power().
    matrix operator!();                     // Overload for Inv();
    matrix operator~();                     // Overload for Tran();
};

const matrix NULL_MATRIX("", 0, 0);

// class R_3d : public matrix
// {
// public:
//     double theta_x, theta_y, theta_z;
//     void Boost(double alpha, double beta, double gramma);
// };

// class R_2d : public matrix
// {
// public:
//     double theta_x;
//     void Boost(double alpha);
// };

typedef matrix mat;
typedef matrix vct;

// typedef matrix R_3;
// typedef matrix R_2;

// vct getColumn(matrix Mt, int c);
// vct getRow(matrix Mt, int r);

bool isZERO(double _X);

double Module(vct va);                           // Calculate the module of a  vector.
matrix Approx(matrix Mt);                        // Approx.
matrix Tran(matrix Mt);                          // Calculate the transpose of matrix.
matrix Power(const matrix Mt, unsigned int pow); // Calculate the power of matrix.
double Dot(vct va, vct vb);
double Angle(vct va, vct vb);
vct Cross(vct va, vct vb);

// double standard_echelon(double matrix[_MAX_SCALE][_MAX_SCALE], int r, int c, int x, int y);
matrix Echelon(matrix Mt);
int Rank(matrix Mt);

matrix Inv(matrix Mt);
matrix Merge_inRow(matrix A, matrix B);    // To combine two matrice together in row.
matrix Merge_inColumn(matrix A, matrix B); // To combine two matrice together in row.

double determinant(double matrix[_MAX_SCALE][_MAX_SCALE], int order);                     // From: https://zhuanlan.zhihu.com/p/305328519
double laplace_expansion(double matrix[_MAX_SCALE][_MAX_SCALE], int r, int c, int order); // From: https://zhuanlan.zhihu.com/p/305328519
double Det(matrix Mt);

#endif